<?php
/** Rho internationalisation module.
*
* This file will normally be loaded automatically by the controller.
* @see Rho_Controller::$need_i18n
*
* @package Rho
* @author Pete
* @version 0.1
* @license http://www.opensource.org/licenses/apache2.0.php Apache License, Version 2.0.*/

/** Provides static methods to aid in internalisation.
* @package Rho */
final class i18n{

  /** @var arryAssocative array of language strings.*/
  private static $_lang=array('core.error'=>'Error');
  /** @var array List of language files loaded.*/
  private static $_lang_files=array();
  /** @var string The default base language*/
  private static $_lang_base_default='en';
  /** @var bool If the base language has changed.*/  
  private static $_lang_base_changed=FALSE;
  
  /** Prevents instances.*/
  private function __construct(){
  }
 
 /** Translation function.
  * @param string $key The string to translate eg. core.error.
  * @param array $args Optional - List of values to include in the output.
  * @param bool $echo Whether to echo the result before returning - default TRUE.
  * @return string The translated string.*/
  public static function tr($key, $args = array(), $echo=TRUE){
    if(isset(self::$_lang[$key]) ){ #already loaded
      $result=sprintf(self::$_lang[$key], $args);
      if($echo) echo $result;
      return $result;
    }
    // Need to load.  First determine the file name from key
    // e.g. $key='foo.bar' then file is 'foo'
    # find first .
    $pos=mb_strpos($key, '.', 0 );
    if(!$pos){ # no . or the first character is a . - shouldn't happen
      $lang_file='core';
      $key='core.'.$key;
      Rho_Log::info('i18n Bad language key of '.$key.' passed to i18n::tr() - assuming core.'); 
    }
    else{
      $lang_file=mb_substr($key, 0, $pos);
    }
    // load the language file if not already loaded
    if(! in_array($lang_file, self::$_lang_files) ){
      self::loadLangFile($lang_file);
      // key should exist now
      if(isset(self::$_lang[$key]) ){
        $result=sprintf(self::$_lang[$key], $args);
        if($echo) echo $result;
        return $result;
      }
    }
    // oops missing key - log, then use the key itself as the result
    Rho_Log::error(1101, 'i18n Missing language key of '.$key.' passed to i18n::tr()');
    if($echo) echo $key;
    return $key;
  }
  
  /** Change the language used.
  * If the base language specified does not exist, as specified in conf 'lang_base_avail', 
  * nothing will change.
  * @param string $base 2 letter language code, e.g. 'en'.
  * @param string $region (Optional) 2 letter country code uppercase, e.g. 'US'.
  * @param string $varient Optional - a language varient such as 'informal'.
  * @return bool TRUE on success.
  * @see tr()
  */
  public static function setLang($base, $region='', $varient=''){
    if(empty ($base) ){
      Rho_Log::error(1102, 'i18n Empty $base passed to i18n::setLang()');
      return FALSE;
    }
    // is i18n enabled?
    if(! Rho::conf('lang.use_i18n') ) return FALSE;
    // does the base lnaguage exist ?
    if(! in_array($base, Rho::conf('lang.base_avail')) ){
      Rho_Log::error(1103, "i18n Base language of $base not available in call to setLang()");
      return FALSE;
    }
    
    // has base langugage has changed for the first time from default ?
    if( (! self::$_lang_base_changed) && $base!=Rho::conf('lang.base') ){ 
      self::$_lang_base_default=Rho::conf('lang.base'); # remember default base
      self::$_lang_base_changed=TRUE;
    }
    
    // set base language
    Rho::setConf('lang.base', $base);
    // region and varient if set    
    if(! empty($region) ){
      if(! in_array($base.'_'.$regions, Rho::conf('lang_region_avail') ) ){
        Rho_Log::error(1104, "i18n Region of $region not available for language $base in call to setLang()");
      }
      else {
        // set region
        Rho::setConf('lang.region', $region);
        // set varient - note we don't do availability checking here
        Rho::setConf('lang.varient', $varient);
      }
    }
    else { # region empty
      Rho::setConf('lang.region', '');
      Rho::setConf('lang.varient', '');
    }
    return TRUE;
  }  
  
  
  /** Attempts to load a language file based on the current lang.
  * This method is called automatically by tr() as needed, and does not normally need to be called manually.
  * If the file does not exist, errors will logged.
  * @param string &$lang_file Name of the translation file to load.
  * @see tr() */
  public static function loadLangFile(& $lang_file){
    $path=Rho::$APP_PATH.'i18n/'.Rho::conf('lang.base').'/';
    if(file_exists($path.$lang_file.'.php') ){
      // load  it!
      include_once $path.$lang_file.'.php';
      // ok, region file if exists (doesn't have to)
      $region=Rho::conf('lang.region', '');
      if( ! empty($region) ){
        $file=$path.$region.'/'.$lang_file.'.php';
        if(file_exists($file) ){
          include_once $file;
        }
        // and a varient if set and exists (doesn't have to)
        $varient=Rho::conf('lang.varient', '');
        if( ! empty($varient) ){
          $file=$path.Rho::conf('lang.region').'/'.Rho::conf('lang.varient').'/'.$lang_file.'.php';
          if(file_exists($file) ){
            include_once $file;
          }
        } #end if varient
      } # end if region
    }
    else { # oops, the base file doesn't exists for the current language
      Rho_Log::error(1100, "i18n Missing translation file $lang_file for language ".Rho::conf('lang.base'));
      // try and load the file for the default language instead as an emergency measure
      $default_file=Rho::$APP_PATH.'i18n/'.self::$_lang_base_default.'/'.$lang_file.'.php';
      if(Rho::conf('lang.base') != self::$_lang_base_default && file_exists($default_file) ){
        include_once $default_file;
      }
    }
    // add to list
    self::$_lang_files[]=$lang_file; 
  }
  
  /** Loads the core language file if not already loaded.*/
  private static function _loadCore(){
    if( in_array('core', self::$_lang_files) ) return; # already loaded
    $file='core';
    self::loadLangFile($file);
  }
  
  /** Formats a number to the current language format.
  * @param int|float $number The number to format.
  * @param int $decimals The number of decimal places to show - default is 0 which works well for integers.
  * @param bool $echo Whether to echo the result before returning - default TRUE.
  * @return string The formatted number.
  * @see currency() */
  public static function number($number, $decimals=0, $echo=TRUE){
    self::_loadCore();
    $result=number_format($number, $decimals, Rho::ifset(self::$_lang, 'core.dec_point', '.' ), Rho::ifset(self::$_lang, 'core.thousands_sep', ',') );
    if($echo) echo $result;
    return $result;
  }
  
  /** Formats an amount to the local currency.
  * @param float|int $number The number to format.
  * @param string $symbol The currency symbol to use. The default of NULL means the local currency symbol will be used.
  * @param bool $echo Whether to echo the result before returning - default TRUE.
  * @return string The formatted number.
  * @see number() */
  public static function currency($number, $symbol=NULL, $echo=TRUE){
    self::_loadCore();
    if($symbol==NULL){
      $symbol=Rho::ifset(self::$_lang, 'core.currency_symbol', '$');
    }
    $result=number_format($number, Rho::ifset(self::$_lang, 'core.currency_decimals', 2), Rho::ifset(self::$_lang, 'core.dec_point', '.' ), Rho::ifset(self::$_lang, 'core.thousands_sep', ',') );
    // symbol before or after number ?
    if( Rho::ifset(self::$_lang, 'currency_symbol_before', TRUE) ){
      $result=$symbol.$result;
    }
    else {
      $result=$result.$symbol;
    }
    if($echo) echo $result;
    return $result;
  }
}